package com.xunmall.base.interceptor.lock;


import com.xunmall.base.annotation.Lockable;
import com.xunmall.base.entity.MessageVo;
import com.xunmall.base.exception.BaseException;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.concurrent.locks.Lock;

/**
 * @Author: WangYanjing
 * @Date: 2018/12/27 10:53
 * @Description:
 */
public abstract class AbstractRequestLockInterceptor {

    protected abstract Lock getLock(String key, long expirationTime);

    protected abstract boolean tryLock(long waitTime, Lock lock) throws InterruptedException;

    protected abstract void unlock(Lock lock);

    @Around("execution(* com.xunmall..*.*(..)) && @annotation(com.xunmall.base.annotation.Lockable)")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        String targetName = point.getTarget().getClass().getName();
        String methodName = point.getSignature().getName();
        Object[] arguments = point.getArgs();

        if (method != null && method.isAnnotationPresent(Lockable.class)) {
            Lockable requestLockable = method.getAnnotation(Lockable.class);

            String requestLockKey = getLockKey(method, targetName, methodName, requestLockable.key(), arguments);
            Lock lock = this.getLock(requestLockKey, requestLockable.expirationTime());
            boolean isLock = this.tryLock(requestLockable.maxWaiteTime(), lock);
            if (isLock) {
                try {
                    return point.proceed();
                } finally {
                    unlock(lock);
                }
            } else {
                MessageVo messages = new MessageVo();
                messages.addMessageObj(BaseException.ERR_9999, "获取锁资源失败", this.getClass().getName());
            }
        }

        return point.proceed();
    }

    private String getLockKey(Method method, String targetName, String methodName, String key, Object[] arguments) {

        StringBuilder sb = new StringBuilder();
        sb.append("lock-").append(targetName).append(".").append(methodName);

        if (StringUtils.isNotEmpty(key)) {
            String keyStr = key;
            if (!StringUtils.isBlank(keyStr)) {
                LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
                String[] parameters = discoverer.getParameterNames(method);
                ExpressionParser parser = new SpelExpressionParser();
                Expression expression = parser.parseExpression(keyStr);
                EvaluationContext context = new StandardEvaluationContext();
                int length = parameters.length;
                if (length > 0) {
                    for (int i = 0; i < length; i++) {
                        context.setVariable(parameters[i], arguments[i]);
                    }
                }
                String keysValue = expression.getValue(context, String.class);
                sb.append("#").append(keysValue);
            }
        }
        return sb.toString();
    }
}
